Web App Architechture

How to simplify a web app using the observer pattern (event listeners), object orientated programming and testing.

Imagine a single page application (SPA) with the following features ...

And there are messages pushed from the server, sometimes out of order.
This can be very complexed with lots of couplings, but it can be made simple with the plugin/observer pattern using event listeners to decouple the code: (see graph below)

Server Event Chat Friends Messages Msg badge

Instead of having an event handler that dispatch events to the models (Messages, Chat, Friends, Badge), the models should listen for events! (see code example below)

(function() {
	// Code for the Messages goes here ...
	
	API.addEventListener("msg_read", readMsg);
	API.addEventListener("msg_new", addMessage);
	
	function readMsg(msg) {
		var id = msg.id;
		if(MESSAGES.hasOwnProperty(msg.id) {
			MESSAGES[msg.id].markRead();
		}
		else {
			API.reportAnomaly("Got msg_read event for non existing msg id=" + msg.id);
			// Could put it in a queue and try marking it as read again in a few seconds
		}
	}
	
	function addMessage(msg) {
		if(!MESSAGES.hasOwnProperty(msg.id) MESSAGES[msg.id] = new Message(msg);
	}
	
})();

(function() {
	// Code for the Badge goes here ...
	
	var badge = document.getElementById("unreadMessages");
	badge.setAttribute("class", "badge unread");
	
	var text = document.createTextNode("0");
	
	API.addEventListener("msg_read", updateBadge);
	API.addEventListener("msg_new", updateBadge);
	
	function updateBadge() {
		var unread = 0;
		
		for(var id in MESSAGES) unread += MESSAGES[id].unread;
		
		badge.removeChild(text);
		text = document.createTextNode(unread);
		badge.appendChild(text);
	}
	
})();
	

With the plugin pattern, each model can operate independent of other models, so there is no complexity increase if you decide to add more functionality.

Don't even bother trying to bypass the server and send events locally. The user might be connected from two devices, like a PC and a mobile phone ... We want the message to be marked as read on both! Animations can be used to hide lag from the server round-trip.

Testing

Have you ever fixed the same bug over and over again ? That is called a regression bug. You fix a bug, then you or someone else add more features, and the bug shows up again.
To prevent this you should write code that automatically repeats the bug to check if it exist.

Lets say there is an annoying bug where the badge reports unread messages, even though there are no unread messages ...
If you have figured out the steps needed to repeat the bug, write code that runs those steps ...

addTest(function unreadMsgBug() {
	MESSAGES = {}; // Clear messages
	
	API.serverMsg("msg_new", {id:1337, subject: "hi"});
	API.serverMsg("msg_read", {id:1337});
	API.serverMsg("msg_new", {id:1337, subject: "hi"});
	
	var badgeText = document.getElementById("unreadMessages").firstChild.textContent;
	
	if(badgeText != "0") throw new Error("badge bug!");
});
	

Do this before fixing the bug, and run it to see that it catches the bug. Then when you have fixed the actual bug, you can run the test again to confirm.

And every time the app gets more updates, you can run all automatic tests to make sure no old bugs are back.

If possible, you should also add a run-time test, that automatically sends a bug report when something bad happens ...

function updateBadge() {
	...
	if(unreadMessages < 0) reportAnomaly(...);
}
	

Written by Oct 30, 2016.


Follow me via RSS:   RSS https://zäta.com/rss_en.xml (copy to feed-reader)
or Github:   Github https://github.com/Z3TA